package parsing.productions;

import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import world.Critter;


/**
 * A representation of a critter program.
 *
 */
public class Program extends AbstractNode implements ExecutableNode{
	//invariant for children: contains any number of Rules, and only Rules

	/**
	 * Creates a new Program
	 * @param rules This Program's Rules
	 * Requires: rules contains only Rules
	 */
	public Program(List<AbstractNode> rules){
		if(rules.size() > 0)
			children= rules;
		else
			throw new IllegalArgumentException("There must be at least one child to make this production.");
	}

	/**
	 * Return a version of the same AST with one random mutation in it.
	 * The new version is a copy and does not affect the original AST.
	 * Requires: this Program contains at least one Rule.
	 */
	public Program mutate(){
		return Program.mutate((Program)duplicate());
	}
	
	/**
	 * Returns toMutate with one mutation. 
	 * Effects: makes the mutation directly to toMutate
	 * @param toMutate the program to make the change to
	 * @return the mutated version of toMutate
	 */
	public static Program mutate(Program toMutate) {
		Random rnd= new Random();
		int mutationType;
		if (toMutate.children.size() == 0)
			return toMutate;
		if (toMutate.children.size() == 1)
			mutationType = rnd.nextInt(2) + 2;//Cannot remove or switch rules if there is only one rule.
		else mutationType= rnd.nextInt(4);
		switch (mutationType) {
		case 0: toMutate.removeRule(); return toMutate;
		case 1: toMutate.switchRules(); return toMutate;
		case 2: toMutate.duplicateRule(); return toMutate;
		default: toMutate.mutateRule(); return toMutate;
		}
	}
	
	//NOTE: changes this program
	private void removeRule() {
//		System.out.println("A rule was removed");
		int numRules= children.size();
		Random rnd= new Random();
		int rule= rnd.nextInt(numRules);
		children.remove(rule);
	}
	
	//NOTE: changes this program
	private void duplicateRule() {
//		System.out.println("A rule was duplicated");
		int numRules= children.size();
		Random rnd= new Random();
		int ruleIndex= rnd.nextInt(numRules);
		Rule duplicateRule= (Rule)children.get(ruleIndex).duplicate();
		children.add(ruleIndex, duplicateRule);
	}
	
	//NOTE: changes this program
	private void switchRules() {
//		System.out.println("Two rules switched places");
		int numRules= children.size();
		Random rnd= new Random();
		int rule1;
		int rule2;
		do {
			rule1= rnd.nextInt(numRules);
			rule2= rnd.nextInt(numRules);
		} while (rule1 == rule2);
		AbstractNode temporary= children.get(rule1);
		children.set(rule1, children.get(rule2));
		children.set(rule2, temporary);
	}
	
	//NOTE: changes this program
	private void mutateRule() {
		int numRules= children.size();
		Random rnd= new Random();
		int ruleIndex= rnd.nextInt(numRules);
		AbstractNode mutated= Rule.mutate(children.get(ruleIndex), this);
		children.set(ruleIndex, mutated);
	}

	@Override
	public void prettyPrint(StringBuffer sb) {
		for(Node n : children){
			n.prettyPrint(sb);
			sb.append("\n\n");
		}
		sb.delete(sb.length() - 2, sb.length());//removes the last two newlines (which are unnecessary)
	}

	@Override
	public Node duplicate() {
		List<AbstractNode> duplicateChildren= new LinkedList<AbstractNode>();
		for (AbstractNode n : children) {
			duplicateChildren.add((AbstractNode)n.duplicate());
		}
		return new Program(duplicateChildren);
	}

	@Override
	public boolean execute(Critter c) {
		boolean rtn = true;
		c.resetRuleCount();
		int rulesExecuted = 0;
		boolean changeMade = false;
		while(!c.hasActed() && c.getMemValue(6) < c.getWorld().MAX_RULES_PER_TURN){
			rulesExecuted = 0;
			changeMade = false;
			c.incrementRuleCount();
			while(rulesExecuted < children.size() && !changeMade){
				changeMade = ((ExecutableNode) children.get(rulesExecuted)).execute(c);
				rulesExecuted++;
			}
			if(rulesExecuted == children.size())//no rules had true conditions
				c.rest();
		}
		if(!changeMade){
			c.setLastRule(null);
			rtn = false;
		}
		else{
			if(rulesExecuted == 0)
				c.setLastRule((Rule) children.get(children.size() - 1));
			else
				c.setLastRule((Rule) children.get(rulesExecuted - 1));
		}
		return rtn;
	}

}
